package nl.windgazer.arachne.internal.transform;

import java.lang.annotation.Annotation;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.Locale;

import org.apache.tapestry5.Asset;
import org.apache.tapestry5.ComponentResources;
import org.apache.tapestry5.annotations.SetupRender;
import org.apache.tapestry5.func.F;
import org.apache.tapestry5.func.Mapper;
import org.apache.tapestry5.func.Worker;
import org.apache.tapestry5.ioc.Resource;
import org.apache.tapestry5.ioc.services.SymbolSource;
import org.apache.tapestry5.model.MutableComponentModel;
import org.apache.tapestry5.runtime.Component;
import org.apache.tapestry5.services.AssetSource;
import org.apache.tapestry5.services.ClassTransformation;
import org.apache.tapestry5.services.ComponentInstanceOperation;
import org.apache.tapestry5.services.ComponentMethodAdvice;
import org.apache.tapestry5.services.ComponentMethodInvocation;
import org.apache.tapestry5.services.FieldAccess;
import org.apache.tapestry5.services.TransformConstants;
import org.apache.tapestry5.services.TransformField;
import org.apache.tapestry5.services.TransformMethod;
import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2;

/**
 * Abstract worker based on T5.2 Importworker (some copy/pasting going on there). Essentially an attempt to make
 * the code reusable for different types of annotations that intend to add Assets to the page.
 * 
 * @author mreuring
 *
 * @param <I>
 */
public abstract class IncludeAbstractWorker<I extends Annotation> implements ComponentClassTransformWorker2 {

	private SymbolSource symbolSource;
	private AssetSource assetSource;

	public IncludeAbstractWorker(SymbolSource symbolSource, AssetSource assetSource) {
		this.symbolSource = symbolSource;
		this.assetSource = assetSource;
	}

    public void transform(ClassTransformation transformation, MutableComponentModel model)
    {
        processClassAnnotationAtSetupRenderPhase(transformation, model);

        for (TransformMethod m : transformation.matchMethodsWithAnnotation(getAnnotationClass()))
        {
            decorateMethod(transformation, model, m);
        }
    }

    private void processClassAnnotationAtSetupRenderPhase(ClassTransformation transformation,
            MutableComponentModel model)
    {
    	I annotation = transformation.getAnnotation(getAnnotationClass());

        if (annotation == null)
            return;

        TransformMethod setupRender = transformation.getOrCreateMethod(TransformConstants.SETUP_RENDER_SIGNATURE);

        decorateMethod(transformation, model, setupRender, annotation);

        model.addRenderPhase(SetupRender.class);
    }

    private void decorateMethod(ClassTransformation transformation, MutableComponentModel model, TransformMethod method)
    {
    	I annotation = method.getAnnotation(getAnnotationClass());

        decorateMethod(transformation, model, method, annotation);
    }

    protected abstract void decorateMethod(ClassTransformation transformation, MutableComponentModel model,
            TransformMethod method, I annotation);
    protected abstract Class<I> getAnnotationClass();

    protected void decorateMethodWithOperation(ClassTransformation transformation, MutableComponentModel model,
            TransformMethod method, String[] paths, Worker<Asset> operation)
    {
        if (paths.length == 0)
            return;

        String[] expandedPaths = expandPaths(paths);

        FieldAccess access = createFieldForAssets(transformation);

        storeLocalizedAssetsAtPageLoad(transformation, model.getBaseResource(), expandedPaths, access);

        addMethodAssetOperationAdvice(method, access, operation);
    }

    protected String[] expandPaths(String[] paths)
    {
        String[] result = new String[paths.length];

        for (int i = 0; i < paths.length; i++) {
			result[i] = symbolSource.expandSymbols(paths[i]);
        }

        return result;
    }

    protected FieldAccess createFieldForAssets(ClassTransformation transformation)
    {
        TransformField field = transformation.createField(Modifier.PROTECTED, List.class.getName(), "includedAssets");

        return field.getAccess();
    }

    protected void storeLocalizedAssetsAtPageLoad(ClassTransformation transformation, final Resource baseResource,
            final String[] expandedPaths, final FieldAccess access)
    {
        ComponentMethodAdvice advice = new ComponentMethodAdvice()
        {
            public void advise(ComponentMethodInvocation invocation)
            {
                invocation.proceed();

                ComponentResources resources = invocation.getComponentResources();

                List<Asset> assets = convertPathsToAssets(baseResource, resources.getLocale(), expandedPaths);

                access.write(invocation.getInstance(), assets);
            }
        };

        transformation.getOrCreateMethod(TransformConstants.CONTAINING_PAGE_DID_LOAD_SIGNATURE).addAdvice(advice);
    }

    protected List<Asset> convertPathsToAssets(final Resource baseResource, final Locale locale, String[] assetPaths)
    {

        return F.flow(assetPaths).map(new Mapper<String, Asset>()
        {
            public Asset map(String assetPath)
            {
                return assetSource.getAsset(baseResource, assetPath, locale);
            }
        }).toList();
    }

    protected void addMethodAssetOperationAdvice(TransformMethod method, final FieldAccess access,
            final Worker<Asset> operation)
    {
        ComponentInstanceOperation advice = new ComponentInstanceOperation()
        {

            @SuppressWarnings("unchecked")
			public void invoke(Component instance)
            {
                List<Asset> assets = (List<Asset>) access.read(instance);

                F.flow(assets).each(operation);
            }
        };

        method.addOperationBefore(advice);
    }

}
